/**
 * 
 */
package algoritmos;

import java.io.BufferedReader;
import java.io.IOException;
import java.util.StringTokenizer;
import java.util.Vector;

import matrices.centro.Clase;
import Jama.Matrix;
import problema.FormaProblema;
import problema.Utilidades;

/**
 * @author Jose Antonio Gonzalez, Ambrin Chaudhary
 *
 */
public class KMeans implements FormaProblema {

    public Vector<Matrix> vectorMuestras;
    public Vector<Clase> vectorCentros;
    public Vector<double[]> vectProb;
    public Vector<double[]> vectProbConvergencia;
    public int lonVect = 0;
    public boolean continuar = true;
    public double epsilon = 0.1;

    /* (non-Javadoc)
     * @see problema.FormaProblema#cargarClases(java.util.Vector, java.util.Vector)
     */
    public void cargarClases(Vector<Clase> inClass, Vector<Matrix> inMuestras) {
        // TODO Auto-generated method stub

        //*************************** no estaba implementado. Ambrin 14/5
    	
    	this.vectProb = new Vector<double[]>();
        this.vectorCentros = inClass;
        this.vectorMuestras = inMuestras;
        if (this.vectorMuestras.size() > 0) {
            this.lonVect = this.vectorMuestras.get(0).getColumnDimension();
        } else {
            this.lonVect = 0;
        }

        int x = 0;
        if (this.vectorCentros.size() > 0) {
            x = this.vectorCentros.get(0).getCentro().getColumnDimension();
        }
        for (int i = 0; i < this.vectorCentros.size(); i++) {
            this.vectorCentros.get(i).deleteAllElemetns();
            double[][] d1 = new double[1][x];
            for (int j = 0; j < x; j++) {
                d1[0][j] = 0.0;
            }
            Matrix m = new Matrix(d1);
            this.vectorCentros.get(i).setCentro(m);
        }
    }

    /* (non-Javadoc)
     * @see problema.FormaProblema#cargarFichero(java.io.BufferedReader)
     */
    public void cargarFichero(BufferedReader inStream) throws IOException {
        this.vectorCentros = new Vector<Clase>();
        this.vectorMuestras = new Vector<Matrix>();
        this.vectProb = new Vector<double[]>();
        
        Clase clase = null;
        boolean leerCentro = false;

        try {

            // BufferedReader entrada = new BufferedReader(new
            // FileReader(fichero));

            boolean primeralinea = true;
            String s = "";
            do {
                s = inStream.readLine();
                if (s == null) {
                    break;
                } else {
                    /*if (leerCentro) {
                    StringTokenizer st = new StringTokenizer(s, ",");
                    
                    int i = 0;
                    int cant = st.countTokens();
                    double d[][] = new double[1][cant];
                    while (st.hasMoreTokens()) {
                    
                    String s1 = st.nextToken();
                    double aux = Double.parseDouble(s1);
                    d[0][i] = aux;
                    i++;
                    }
                    clase.setCentro(new Matrix(d));
                    this.vectorCentros.add(clase);
                    leerCentro = false;
                    }
                    
                    else
                     */ if (s.contains("centro:")) 
                     {
                        clase = new Clase();
                        this.vectorCentros.add(clase);
                    //leerCentro = true;

                    } else {
                        StringTokenizer st = new StringTokenizer(s, ",");

                        if (primeralinea) {
                            this.lonVect = st.countTokens();
                            primeralinea = false;
                        }


                        int i = 0;
                        int cant = st.countTokens();
                        double d[][] = new double[1][cant];
                        while (st.hasMoreTokens()) {

                            String s1 = st.nextToken();
                            double aux = Double.parseDouble(s1);
                            d[0][i] = aux;
                            i++;
                        }
                        Matrix m = new Matrix(d);
                       /* if (clase != null) {
                            clase.addElement(m);
                        }*/
                        this.vectorMuestras.add(m);
                    }
                }
            } while (true);

            int x = this.vectorMuestras.get(0).getColumnDimension();
            int y = this.vectorMuestras.get(0).getRowDimension(); 
            for(int i = 0; i<this.vectorCentros.size();i++)
            {
                Matrix m = new Matrix(y, x);
                this.vectorCentros.get(i).setCentro(m);
            }
        } catch (IOException e) {
            
            e.printStackTrace();
            System.out.println("Error al cargar archivo");
            throw e;
        }

    }

    /* (non-Javadoc)
     * @see problema.FormaProblema#ciclo(java.util.Vector)
     */
    public Clase ciclo(Vector<Double> m) {

        Matrix xi = Utilidades.toMatrix(m);
        double sum = 0.0;
        // calsulamos el sumatorio de distancia rspecto al los centros
        for (int j = 0; j < this.vectorCentros.size(); j++) {
            Matrix c = this.vectorCentros.get(j).getCentro();
            sum += 1 / Utilidades.norma2alcuadrado(xi, c);
        }

        double min = Double.MIN_VALUE;
        int centro = -1;

        for (int j = 0; j < this.vectorCentros.size(); j++) {
            Matrix c = this.vectorCentros.get(j).getCentro();
            double num1 = 1 / Utilidades.norma2alcuadrado(xi, c);
            double aux = num1 / sum;
            min = Math.max(min, aux);
            if (min == aux) 
            {
                centro = j;
            }
        }
        
        Matrix m1 = this.media(centro);
        Matrix m2 = this.vectorCentros.get(centro).getCentro();

        this.vectorCentros.get(centro).addElement(xi);
        this.vectorCentros.get(centro).setCentro(m1);
        //this.vectorCentros.get(centro)
        return this.vectorCentros.get(centro);
    }

    private void clonarVectConverg() {
        this.vectProbConvergencia = new Vector<double[]>();
        int centros = this.vectorCentros.size();
        for (int i = 0; i < this.vectProb.size(); i++) {
            double[] d = new double[centros];
            double[] dAux = this.vectProb.get(i);
            for (int j = 0; j < centros; j++) 
            {

                d[j] = dAux[j];
            }
            this.vectProbConvergencia.add(d);
        }
        
        for (int i = 0; i < centros; i++) {
        	this.vectorCentros.get(i).deleteAllElemetns();
        }
    }

    public void evaluarConvergencia() {
        for (int i = 0; i < this.vectProb.size(); i++) {
            for (int j = 0; j < this.vectorCentros.size(); j++) {
                double d1 = this.vectProb.get(i)[j];
                double d2 = this.vectProbConvergencia.get(i)[j];
                double aux = Math.abs(d1 - d2);
                if (aux <= this.epsilon) {
                    this.continuar = false;
                }
            }

        }
    }

    /* (non-Javadoc)
     * @see problema.FormaProblema#entrenar(java.util.Vector)
     */
    public void entrenar(Vector<Double> listaParametros) {
        Matrix m;
        this.inicializaVectProb(this.vectorCentros.size());
        for (int i = 0; i < this.vectorCentros.size(); i++) {
            m = this.mediaInicio(i);
            this.vectorCentros.get(i).setCentro(m);
        }
        this.clonarVectConverg();

        while (this.continuar) {
        	
        	this.clonarVectConverg();
        	
            for (int i = 0; i < this.vectorMuestras.size(); i++) {
                Matrix xi = this.vectorMuestras.get(i);

                double sum = 0.0;
                // calculamos el sumatorio de distancia respecto al los centros
                for (int j = 0; j < this.vectorCentros.size(); j++) {
                    Matrix c = this.vectorCentros.get(j).getCentro();
                    sum += 1 / Utilidades.norma2alcuadrado(xi, c);
                }
                double aux = Double.MIN_VALUE;
                int centro = 0;
                for (int j = 0; j < this.vectorCentros.size(); j++) {
                    Matrix c = this.vectorCentros.get(j).getCentro();
                    double[] d = this.vectProb.get(i);
                    double num1 = 1 / Utilidades.norma2alcuadrado(xi, c);
                    d[j] = num1 / sum;
                    double auxd = d[j];
                    double max = Math.max(aux, d[j]);
                    if(max == auxd)
                    	centro = j;
                    aux = max;

                }
                
                this.vectorCentros.get(centro).addElement(xi);
                
            }

            this.evaluarConvergencia();
            
        }




    }

    /* (non-Javadoc)
     * @see problema.FormaProblema#getClases()
     */
    public Vector<Clase> getClases() {
        return this.vectorCentros;
    }

    public void inicializaVectProb(int x) {
        for (int i = 0; i < this.vectorMuestras.size(); i++) {
            int centros = this.vectorCentros.size();
            double[] d = new double[centros];

            for (int j = 0; j < centros; j++) {
                d[j] = 0.0;
            }
            this.vectProb.add(d);
        }
    }

    public Matrix mediaInicio(int centro) {

        int x = this.vectorMuestras.get(0).getColumnDimension();
        int y = this.vectorMuestras.get(0).getRowDimension();
        
        //Matrix m = new Matrix(x, y);
        Matrix numerador = new Matrix(y, x);
        double denominador = 0.0;
        Clase c = this.vectorCentros.get(centro);
        int numCentros = this.vectorCentros.size();
        int numMuestras= this.vectorMuestras.size();
        int frac = numMuestras /numCentros;
        
        
        for (int i = 0; i < this.vectorMuestras.size(); i++) {
            double prob = 0.0;
            Matrix mAux = this.vectorMuestras.get(i);
            
            int topeBajo = frac * centro;
            
            int topeAlto = 0;
            if( centro == (numCentros -1))
            	topeAlto = numMuestras;
            else
            	topeAlto= topeBajo + frac;
            
            if(i < topeAlto && i >= topeBajo)
            	prob = 0.8;
            else if (this.vectorCentros.size() > 1) 
                prob = (1 - 0.8) / (this.vectorCentros.size() - 1);
             else 
                System.err.println("Error raro en calculo de medias inicial");
            
            
            this.vectProb.get(i)[centro] = prob;
            Matrix mPlus = mAux.times((prob * prob));
            numerador = numerador.plus(mPlus);
            denominador += prob * prob;
        }
        numerador = numerador.times(1 / denominador);
        return numerador;
    }

    public Matrix media(int centro) {

        int x = this.vectorMuestras.get(0).getColumnDimension();
        int y = this.vectorMuestras.get(0).getRowDimension();
        this.inicializaVectProb(x);
        //Matrix m = new Matrix(x, y);
        Matrix numerador = new Matrix(y, x);
        double denominador = 0.0;
        Clase c = this.vectorCentros.get(centro);
        for (int i = 0; i < this.vectorMuestras.size(); i++) {
            double prob = 0.0;
            Matrix mAux = this.vectorMuestras.get(i);

            prob = this.vectProb.get(i)[centro];
            Matrix exp = mAux.times(prob * prob);
            numerador = numerador.plus(exp);
            denominador += prob * prob;
        }
        numerador = numerador.times(1 / denominador);
        return numerador;
    }




	
	public int longVect() {
		
		return this.lonVect;
	}
	
	
	public String toStringCentros() {
		String s = "";
		Clase c = null;
		for (int i = 0; i < this.vectorCentros.size(); i++) {
			s += this.vectorCentros.get(i).toString() + "\n";
			// Utilidades.matrixToString(c.getCentro()) + "\n";
		}

		return s;
	}

	public String toStringMatrices() {
		String s = "";
		Matrix m = null;
		for (int i = 0; i < this.vectorMuestras.size(); i++) {
			m = this.vectorMuestras.get(i);
			s += Utilidades.matrixToString(m) + "\n";
		}

		return s;
	}

}
